
Imports System.Net.Sockets

Namespace tcpTrans
    Public Module tcpTrans
        Private TCP_SEND_BYTES_MS As Int64
        Private TCP_RECEIVE_BYTES_MS As Int64
        Public ReadOnly dataSizePrefixLength As Int32 = sizeof(Of Int64)()
        Public Const defaultSleepTime As Int32 = 1

        Public Sub closeTcpClient(ByRef client As TcpClient)
            If Not client Is Nothing Then
                On Error Resume Next
#If PocketPC OrElse Smartphone Then
                If client.Client.Connected Then
#Else
                If client.Connected OrElse client.Client.Connected Then
#End If
                    closestream(client.GetStream())
                    client.Client.Shutdown(SocketShutdown.Both)
                    client.Client.Close()
                End If
                client.Close()
                client = Nothing
            End If
        End Sub

        Public Function clientClosed(ByVal client As TcpClient) As Boolean
            Dim rtn As Boolean = False
#If PocketPC OrElse Smartphone Then
            rtn = client Is Nothing OrElse client.Client Is Nothing OrElse Not client.Client.Connected
#Else
            rtn = client Is Nothing OrElse client.Client Is Nothing OrElse _
                  Not client.Connected OrElse Not client.Client.Connected
#End If

#If CANNOTDETERMINE Then
            Dim rtn As Boolean = False
            rtn = client Is Nothing OrElse client.Client Is Nothing

            If Not rtn Then
                Try
                    'peek first
                    Dim timeout As Int32
                    Dim block As Boolean
                    timeout = client.Client.ReceiveTimeout()
                    block = client.Client.Blocking()
                    client.Client.ReceiveTimeout = 10
                    client.Client.Blocking = False
                    Static b(0) As Byte
                    client.Client.Receive(b, 1, SocketFlags.Peek)
                    client.Client.ReceiveTimeout() = timeout
                    client.Client.Blocking = block
                Catch
                End Try

                rtn = client Is Nothing OrElse client.Client Is Nothing OrElse _
                      Not client.Connected OrElse Not client.Client.Connected
            End If

            If Not rtn Then
                Dim timeout As Int32
                timeout = client.Client.SendTimeout()
                Try
                    client.Client.SendTimeout = defaultSleepTime
                    Static b(-1) As Byte
                    client.Client.Send(b)
                    client.Client.SendTimeout = timeout
                Catch e As SocketException
                    client.Client.SendTimeout = timeout
                    If e.NativeErrorCode = SocketError.WouldBlock Then
                        Return False
                    Else
                        Return True
                    End If
                Catch
                    Return True
                End Try
            End If
#End If

            Return rtn
        End Function

        Public Function send(ByVal tcpClient As TcpClient, ByVal data As nettransData _
                             , ByVal holdTime As Int64) As Boolean
            If clientClosed(tcpClient) Then
                raiseError(errorHandle.displayMode.console, "client is not connected to " + _
                            remoteEndPoint(tcpClient), errorHandle.errorType.exclamation)
                Return False
            End If
            If data Is Nothing Then
                raiseError(errorHandle.displayMode.console, "do not get valid nettransData." _
                           , errorHandle.errorType.exclamation)
                Return False
            End If

            Dim stream As NetworkStream = Nothing
            Dim startTime As Int64 = ticksToMilliseconds(Now.Ticks)
            Dim buff() As Byte = Nothing
            Dim size As Int64
            Dim rtn As Boolean
            stream = tcpClient.GetStream()
            stream.ReadTimeout = holdTime
            stream.WriteTimeout = holdTime
            Dim tmp As bytes = Nothing
            If data.export(buff, dataSizePrefixLength, size) AndAlso
               convertor.tobytes(size, tmp) Then
                If isdebugmode() Then
                    assert(tmp.size() = dataSizePrefixLength)
                End If
                memcpy(buff, 0, +tmp, 0, dataSizePrefixLength)
                If writestream(stream, buff, starttime:=startTime, holdtime:=holdTime, sleeptime:=defaultSleepTime) Then
                    Dim timems As Int64
                    timems = ticksToMilliseconds(Now.Ticks) - startTime
                    timems = ifoper(timems = 0, 1, timems)
                    counter.increase(TCP_SEND_BYTES_MS, (size + dataSizePrefixLength) \ timems)
                    rtn = True
                Else
                    raiseError(errorHandle.displayMode.console, "failed to send data to " + _
                                remoteEndPoint(tcpClient), errorHandle.errorType.exclamation)
                    rtn = False
                End If
            Else
                raiseError("cannot convert size to bytes, size " + Convert.ToString(size) _
                            , errorHandle.errorType.exclamation)
                rtn = False
            End If

            [erase](buff)
            Return rtn
        End Function

        Public Function receive(ByVal tcpClient As TcpClient, ByRef data As nettransData, _
                                ByVal holdTime As Int64, _
                                Optional ByVal needOvertimeOrNotConnectedInfo As Boolean = True, _
                                Optional ByVal needClearData As Boolean = True) As Boolean
            If clientClosed(tcpClient) Then
                If needOvertimeOrNotConnectedInfo Then
                    raiseError(errorHandle.displayMode.console, "client is not connected to " + _
                                remoteEndPoint(tcpClient), errorHandle.errorType.exclamation)
                End If
                Return False
            End If

            Dim stream As NetworkStream = Nothing
            Dim startTime As Int64 = ticksToMilliseconds(Now.Ticks)
            Dim buff As bytes = Nothing
            Dim dataLength As Int64

            stream = tcpClient.GetStream
            stream.ReadTimeout = holdTime
            stream.WriteTimeout = holdTime
            If data Is Nothing Then
                data = New nettransData()
            ElseIf needClearData Then
                data.clear()
            End If
            While Not stream Is Nothing AndAlso Not stream.DataAvailable _
                  AndAlso ticksToMilliseconds(Now.Ticks) - startTime < holdTime
                sleep(defaultSleepTime)
            End While

            Dim rtn As Boolean = False
            If ticksToMilliseconds(Now.Ticks) - startTime < holdTime Then
                If readstream(stream, buff, , dataSizePrefixLength, , _
                              startTime, holdTime, defaultSleepTime) = dataSizePrefixLength Then
                    If Not convertor.toint64(buff, dataLength) OrElse dataLength < 0 Then
                        raiseError(errorHandle.displayMode.console, _
                                   "cannot get head information, skip receiving from " + _
                                    remoteEndPoint(tcpClient), errorHandle.errorType.exclamation)
                        rtn = False
                    Else
                        If readstream(stream, buff, , dataLength, , startTime, _
                                      holdTime, defaultSleepTime) = dataLength Then
                            Dim timems As Int64
                            timems = ticksToMilliseconds(Now.Ticks) - startTime
                            timems = ifoper(timems = 0, 1, timems)
                            counter.increase(TCP_RECEIVE_BYTES_MS, (dataLength + dataSizePrefixLength) \ timems)
                            data.import(buff, 0, dataLength)
                            rtn = True
                        Else
                            raiseError(errorHandle.displayMode.console, "failed to receive data from " + _
                                       remoteEndPoint(tcpClient), errorHandle.errorType.exclamation)
                            rtn = False
                        End If
                    End If
                Else
                    raiseError(errorHandle.displayMode.console, _
                               "failed to receive head information, skip receiving from " + _
                                remoteEndPoint(tcpClient), errorHandle.errorType.exclamation)
                    rtn = False
                End If
            Else
                If needOvertimeOrNotConnectedInfo Then
                    raiseError(errorHandle.displayMode.console, "receiving timeout, skip receive from " + _
                                remoteEndPoint(tcpClient), errorHandle.errorType.exclamation)
                End If
                rtn = False
            End If
            Return rtn
        End Function

        Sub New()
            TCP_RECEIVE_BYTES_MS = counter.registerAverageAndLastAverage("TCP_RECEIVE_BYTES_MS", _
                                                                         sampleRate:=0.01)
            TCP_SEND_BYTES_MS = counter.registerAverageAndLastAverage("TCP_SEND_BYTES_MS", _
                                                                      sampleRate:=0.01)
        End Sub
    End Module
End Namespace
